package com.gaohaiyan.javastructpack;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * www.gaohaiyan.com
 */
public class StructMan {

    /**
     * 字节对齐顺序
     * 一块内存区域，被划分为存储数据的一系列空间，每个空间都有一个索引，索引由小到大。
     * 小端：数据的低位字节从内存的低位字节开始存储。
     * 【打包采用哪种字节对齐方式，解包就采用相同方式】
     * <p>
     * 0, fmt="!3sHf"  # ! 叹号开头，网络传输数据常用的大端方式
     * 1, fmt=">3sHf"  # > 大于号开头，大端
     * 2, fmt="<3sHf"  # < 小于号开头，小端
     */
    private int align = 0;

    /**
     * python的struct库设定：
     * <p>
     * 字符串：按照普通字符串操作，使用中，有多少字符就用多少‘s’占位
     * str = "ABC".encode('utf-8')
     * 占位规则：
     * str_paceholder = "3s"  # 长度3，可以使用‘3s’占位，也可以使用‘sss’占位。
     *
     * <p>
     * 字符：
     * char = "D"  # 只有一个字符，同C语言的char。根据开发需求，须要占1位使用‘c’；否则如上按普通字符串操作。
     * 占位规则：
     * char_paceholder = "c"
     * char_paceholder = "1s"
     *
     * <p>
     * 整型：
     * int = 99  # python中只有int一个类型。而对于C语言中short、int、long有不同长度。根据开发需求占几位，使用不同的占位符
     * 占位规则：
     * int_paceholder = "h"  # 对应C中的short，占2位
     * int_paceholder = "H"  # 对应C中的无符号short，占2位
     * int_paceholder = "i"  # 对应C中的int，占4位
     * int_paceholder = "I"  # 对应C中的无符号int，占4位
     * int_paceholder = "l"  # 对应C中的long，占4位
     * int_paceholder = "L"  # 对应C中的无符号long，占4位
     * int_paceholder = "q"  # 对应C中的long long，占8位
     * int_paceholder = "Q"  # 对应C中的无符号long long，占8位
     *
     * <p>
     * 浮点型：
     * float = 9.9  # python中只有float一个浮点型
     * 占位规则：
     * float_paceholder = "f"   # 对应C中的float，占4位
     * float_paceholder = "D"   # 对应C中的double，占8位
     *
     * <p>
     * 示例需求：长度为3的字符串+占2位的数字+占4位的浮点数字
     * fmt="3sHf" # 封装规则。目标数据是 ABC 99 9.9 ，按顺序排列占位符
     *
     * @param format 打包规则
     * @param params 要打包的数据
     */
    public String pack(String format, Object... params) {
        StringBuilder sb = new StringBuilder();
        boolean legitimacy = isFormatsLegitimacy(format); // 检查规则可用性
        if (legitimacy) {
            List<String> elements = getFormatElements(format); // 提取规则
            if (elements.size() != params.length) {
                throw new RuntimeException("要打包的数据量须匹配规则");
            }

            for (int i = 0; i < elements.size(); i++) {
                String fmt = elements.get(i);
                Object obj = params[i];
                String bytes = obj2byte(fmt, obj); // 按规则转换每个数据
                sb.append(bytes);
            }
        }

        return sb.toString();
    }

    /**
     * 规则是否有效
     *
     * @param format
     * @return
     */
    private boolean isFormatsLegitimacy(String format) {

        boolean mte = Pattern.matches("^[hHiIlLqQfD0-9<>!s]*$", format); // 是否存在可用占位字符之外的字符
        if (!mte) {
            throw new RuntimeException("非法规则：使用了非法的占位字符");
        }

        mte = Pattern.matches("^[<>!]{2,}.+", format); // 对齐占位符的数量是否大于1
        if (mte) {
            throw new RuntimeException("非法规则：对齐方式须在规则开头且只设定1种");
        }

        mte = Pattern.matches(".+[<>!]+.+", format); // 开头字符不是对齐占位符
        if (mte) {
            throw new RuntimeException("非法规则：对齐方式须在规则开头且只设定一种");
        }

        mte = Pattern.matches(".*\\d+.*", format); // 存在数字
        if (mte) {
            Pattern pattern = Pattern.compile("(\\d+\\D{0,1})"); // 取出数字和后面的第一个字符
            Matcher matcher = pattern.matcher(format);
            while (matcher.find()) {
                matcher.start();
                String ele = matcher.group(1);
                if (!ele.endsWith("s")) {
                    throw new RuntimeException("非法规则：数字只能修饰's'");
                }
            }
        }

        return true;
    }

    /**
     * 规则分析
     *
     * @param format
     * @return 规则对应的参数
     */
    private List<String> getFormatElements(String format) {
        List<String> eles = new ArrayList<>();
        StringBuilder sb = new StringBuilder();
        boolean lastCharIsS = false;
        boolean lastCharIsNumber = false;
        char[] chars = format.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            char f = chars[i];

            if ('!' == f) {
                align = 0;
                lastCharIsS = false;
                lastCharIsNumber = false;
            } else if ('>' == f) {
                align = 1;
                lastCharIsS = false;
                lastCharIsNumber = false;
            } else if ('<' == f) {
                align = 2;
                lastCharIsS = false;
                lastCharIsNumber = false;
            } else if (Character.isDigit(f)) { // 数字
                if (lastCharIsS) {
                    eles.add(sb.toString());
                    sb.delete(0, sb.length());
                    sb.append(f);
                } else {
                    sb.append(f);
                }
                lastCharIsS = false;
                lastCharIsNumber = true;
            } else if ('s' == f) {
                sb.append(f); // sss这种同python，即3s

                if (lastCharIsNumber) { // python的struct中'3ss'这种会看做3s、s两个占位规则
                    eles.add(sb.toString());
                    sb.delete(0, sb.length());
                }

                lastCharIsS = true;
                lastCharIsNumber = false;
            } else {
                if (lastCharIsS) {
                    String sf = sb.toString();
                    if (sf.length() > 0) {
                        eles.add(sf);
                        sb.delete(0, sb.length());
                    }
                }

                eles.add(String.valueOf(f));

                lastCharIsS = false;
                lastCharIsNumber = false;
            }
        }

        System.out.println(eles.toString());

        return eles;
    }

    /**
     * 针对每个数据进行转换
     *
     * @param fmt    规则
     * @param object 数据
     * @return
     */
    private String obj2byte(String fmt, Object object) {
        String prefix; // 转换前缀
        String data = object.toString();
        if (fmt.contains("s")) { // 字符串
            if (!(object instanceof Character) && !(object instanceof String)) {
                throw new RuntimeException("'s'占位符不可对应非字符串类型、非字符类型：" + object);
            }

            int length; // 规则中设定的数据长度
            if (fmt.matches("^[s]+$")) { // sss
                length = fmt.length();
            } else { // 3s
                String num = fmt.replace("s", "");
                length = Integer.valueOf(num).intValue(); // try NumberFormatException
            }
            if (length != data.length()) {
                throw new RuntimeException("'s'占位符数量与数据长度不匹配：" + length + "——" + object);
            }
            prefix = "/sx";
        } else if ("h".equals(fmt) || "H".equals(fmt)) { // short 类型
            prefix = "/hx";
        } else if ("i".equals(fmt) || "I".equals(fmt)) { // int 类型
            prefix = "/ix";
        } else if ("l".equals(fmt) || "L".equals(fmt) || "q".equals(fmt) || "Q".equals(fmt)) { // long long 类型
            prefix = "/lx";
        } else if ("f".equals(fmt)) { // float 类型
            prefix = "/fx";
        } else if ("D".equals(fmt)) { // double 类型
            prefix = "/dx";
        } else {
            prefix = "/0x";
        }

        return str2HexString(prefix, data);
    }

    /**
     * 字符串转16进制字符串
     *
     * @param prefix 转换前缀
     * @param data   字符串数据
     * @return
     */
    private String str2HexString(String prefix, String data) {
        System.out.println("前缀：" + prefix + "，数据：" + data);
        StringBuffer hexString = new StringBuffer();
        for (int i = 0; i < data.length(); i++) {
            int ch = (int) data.charAt(i);
            String strHex = Integer.toHexString(ch);
            hexString.append(prefix + strHex);
        }
        return hexString.toString();
    }
}
